#include "Tablero.h"

	Fila::Fila(unsigned int ubicacion,unsigned int rango){
		this->ubicacion=ubicacion;
		this->rango=rango;
	}
	Fila::~Fila(){

	}
	//pre: ubicacion dentro del rango de la fila
	//post: devuelve una copia del casillero ubicado en ubicacion
	// el casillero estara vacio si no hay un casillero lleno o se excedio del fin de la fila
	Casillero Fila::obtenerCasillero(unsigned int ubicacion){
		Casillero casilleroADevolver;
		list<Casillero>::iterator iterador;
		casilleroADevolver.vaciarCasillero();
		unsigned int posicionEnLista=1;
		if ((ubicacion<rango)&&(!casilleros.empty())){
			iterador = casilleros.begin();
			while(((*iterador).posicionHorizontal<ubicacion) && (posicionEnLista<=casilleros.size())){
				iterador++;
				posicionEnLista++;
			}
			if ((*iterador).posicionHorizontal==ubicacion){
				casilleroADevolver.llenarCasillero((*iterador).getColor());
				casilleroADevolver.posicionHorizontal=ubicacion;
			}
		}
		return casilleroADevolver;
	}

	//pre: ubicacion dentro del rango de la fila
	//post: llena el casillero ubicado en ubicacion, independientemente de su estado anterior
	void Fila::llenarCasillero(unsigned int ubicacion){
		Casillero casilleroALLenar;
		Color color;
		casilleroALLenar.llenarCasillero(color);
		casilleroALLenar.posicionHorizontal=ubicacion;
		list<Casillero>::iterator iterador;
		unsigned int posicionEnLista=1;
		if ((ubicacion<rango)&&(!casilleros.empty())){
			iterador= casilleros.begin();
			while(((*iterador).posicionHorizontal<ubicacion) && (posicionEnLista<=casilleros.size())){
				iterador++;
				posicionEnLista++;
			}
			if ((*iterador).posicionHorizontal>ubicacion){ //si posHorizontal es mayor, se paso y hay que insertar.
				casilleros.insert(iterador,casilleroALLenar);
			}
			if ((*iterador).posicionHorizontal<ubicacion){ //Si es menor,llego al ultimo elemento.
				casilleros.push_back(casilleroALLenar);
			}
			//Si es igual, ya esta y no hay que hacer nada
		}
		if(casilleros.empty()){
			casilleros.push_back(casilleroALLenar);
		}
	}

	//pre: ubicacion dentro del rango de la fila
	//post: vacia el casillero ubicado en ubicacion, independientemente de su estado anterior
	void Fila::vaciarCasillero(unsigned int ubicacion){
		list<Casillero>::iterator iterador;
		unsigned int posicionEnLista=1;
		if((ubicacion<rango)&&(!casilleros.empty())){
			while(((*iterador).posicionHorizontal<ubicacion) && (posicionEnLista<=casilleros.size())){
				iterador++;
				posicionEnLista++;
			}
			if ((*iterador).posicionHorizontal==ubicacion){
				casilleros.erase(iterador);
			}
		}
	}

	//post: devuelve la ubicacion de la Fila en una matriz
	unsigned int Fila::obtenerPosicionEnMatriz(){
		return this->ubicacion;
	}

MatrizDeListas::MatrizDeListas(unsigned int cantidadColumnas,unsigned int cantidadFilas){
	Fila* pfila;

	this->cantidadFilas=cantidadFilas;
	this->cantidadColumnas=cantidadColumnas;

	for(unsigned int i=0;i<this->cantidadFilas;i++){
		pfila= new Fila(i,cantidadColumnas);
		filas.push_back(pfila);
	}


}
MatrizDeListas::~MatrizDeListas(){
	while(!filas.empty()){
		delete filas.back();
		filas.pop_back();
	}
}
Casillero MatrizDeListas::obtenerCasillero(unsigned int columna,unsigned int fila){
	Casillero casilleroADevolver;
	casilleroADevolver.vaciarCasillero();
	list<Fila*>::iterator iterador= filas.begin();

	if ((columna>=this->cantidadColumnas) || (fila>=this->cantidadFilas)) return casilleroADevolver;

	while((*iterador)->obtenerPosicionEnMatriz() < fila){
		iterador++;
	}
	casilleroADevolver=(*iterador)->obtenerCasillero(columna);

	return casilleroADevolver;

}
void MatrizDeListas::llenarCasillero(unsigned int columna, unsigned int fila){
	list<Fila*>::iterator iterador= filas.begin();

	if ((columna>=this->cantidadColumnas) || (fila>=this->cantidadFilas)) return;

	while((*iterador)->obtenerPosicionEnMatriz() < fila){
		iterador++;
	}
	(*iterador)->llenarCasillero(columna);


}
void MatrizDeListas::vaciarCasillero(unsigned int columna, unsigned int fila){
	list<Fila*>::iterator iterador= filas.begin();

	if ((columna>=this->cantidadColumnas) || (fila>=this->cantidadFilas)) return;

	while((*iterador)->obtenerPosicionEnMatriz() < fila){
		iterador++;
	}
	(*iterador)->vaciarCasillero(columna);
}





Tablero::Tablero() {
	this->cantidadColumnas=0;
	this->cantidadFilas=0;
	this->tablero=NULL;
	this->alturaDeFilaActual=0;
	this->posicionHorizontalDeNuevaPieza=0;
	this->posicionVerticalDeNuevaPieza=0;

}

void Tablero::setDimensiones(unsigned int dimensionX,unsigned int dimensionY){
	if ((dimensionX==0)||(dimensionY==0)) return;
	if (this->tablero!=NULL){
		for(unsigned int i=0;i<this->cantidadColumnas;i++){
			delete this->tablero;							//destruye el tablero anterior si se vuelve a llamar este metodo
		}
		delete[] this->tablero;
	}

	this->cantidadColumnas=dimensionX;
	this->cantidadFilas=dimensionY;
	this->tablero= new MatrizDeListas(dimensionX,dimensionY);
}


void Tablero::setCasillero(Casillero c,unsigned int ubicacionX,unsigned int ubicacionY){
	//this->tablero[ubicacionX][ubicacionY]=c;
	this->tablero->llenarCasillero(ubicacionX,ubicacionY);
}


Casillero Tablero::getCasillero(unsigned int ubicacionX,unsigned int ubicacionY){
	Casillero casilleroVacio;
	if ((this->tablero!=NULL)&&(ubicacionX<this->cantidadColumnas)&&(ubicacionY<this->cantidadFilas)){
		return (this->tablero->obtenerCasillero(ubicacionX,ubicacionY));
	} else return casilleroVacio;
}

bool Tablero::insPieza(Pieza *pieza,unsigned int ubicacionX,unsigned int ubicacionY){    //funcion auxiliar, devuelve true si pudo insertar la pieza, false si hubo una colision
	Pieza* piezaInsertada;
	bool colision=false;
	//primero comprueba que no haya una colision en la zona donde se pondra la pieza
	for(unsigned int i=0;i<pieza->getCantidadColumnas();i++){
		for(unsigned int j=0;j<pieza->getCantidadFilas();j++){
			colision= colision || ((pieza->getCasillero(i,j).getEstado()) && (this->getCasillero(ubicacionX+i,ubicacionY+j).getEstado()));
		}
	}
	//si no hay una colision, "pega" la pieza en el tablero
	if(!colision){
		for(unsigned int i=0;i<pieza->getCantidadColumnas();i++){
			for(unsigned int j=0;j<pieza->getCantidadFilas();j++){
				if (pieza->getCasillero(i,j).getEstado()) {     //solo "pega" los casilleros llenos de la pieza, sino pegaria cualquier cosa
					this->setCasillero(pieza->getCasillero(i,j),ubicacionX+i,ubicacionY+j);
				}
			}
		}
		piezaInsertada=new Pieza(*pieza);
		piezaInsertada->asignarPosicionHorizontal(ubicacionX);
		piezaInsertada->asignarPosicionVertical(ubicacionY);
		piezasInsertadas.push_back(piezaInsertada);
		return true; //si se pudo insertar la pieza sin colision
	}
	return false; // si llega aca significa que hubo colision y se salto el if


}

bool Tablero::insertarPieza(Pieza *pieza){

	bool seLogroInsertar=false;
	unsigned int intentoDeInsercion=1;

	//No, porque se supone que la pieza va a estar limitada por sus filas y columnas, es decir que si o si va a tener
	//al menos un casillero "lleno" en los cuatro bordes
	if ((pieza==NULL)||(pieza->getCantidadColumnas()==0)||(pieza->getCantidadFilas()==0)||(this->tablero==NULL)) return false;

	while((!seLogroInsertar)&&(intentoDeInsercion<5)){
		for(int ubicacionY=0;(ubicacionY<=(this->cantidadFilas - pieza->getCantidadFilas()) && (!seLogroInsertar));ubicacionY++){
			for(int ubicacionX=0;(ubicacionX<=(this->cantidadColumnas - pieza->getCantidadColumnas()) && (!seLogroInsertar));ubicacionX++){
				if (!seLogroInsertar) seLogroInsertar=this->insPieza(pieza,ubicacionX,ubicacionY);
			}
		}
		pieza->rotar();
		intentoDeInsercion++;
	}

	return seLogroInsertar;

}

bool Tablero::insertarPiezaTP1(Pieza *pieza){

	if ((pieza==NULL)||(pieza->getCantidadColumnas()==0)||(pieza->getCantidadFilas()==0)||(this->tablero==NULL) || (pieza->getCantidadFilas() > (this->posicionVerticalDeNuevaPieza + this->cantidadFilas))){
		return false;
	}

	if (((this->posicionHorizontalDeNuevaPieza + pieza->getCantidadColumnas()) <= this->cantidadColumnas) && insPieza(pieza,this->posicionHorizontalDeNuevaPieza,this->posicionVerticalDeNuevaPieza)){
		if (this->alturaDeFilaActual < pieza->getCantidadFilas()){
			this->alturaDeFilaActual=pieza->getCantidadFilas();
		}
		this->posicionHorizontalDeNuevaPieza=this->posicionHorizontalDeNuevaPieza + pieza->getCantidadColumnas();
		return true;
	} else{
		if (((pieza->getCantidadFilas() + this->alturaDeFilaActual + this->posicionVerticalDeNuevaPieza) <= (this->cantidadFilas)) && (insPieza(pieza,0,this->posicionVerticalDeNuevaPieza + this->alturaDeFilaActual))){

			this->posicionHorizontalDeNuevaPieza=pieza->getCantidadColumnas();
			this->posicionVerticalDeNuevaPieza=this->posicionVerticalDeNuevaPieza + this->alturaDeFilaActual;
			this->alturaDeFilaActual=pieza->getCantidadFilas();
			return true;
		} else return false;
	}
}

float Tablero::calcularNivelDeOcupacion(){

	float cantidadDeCasillerosLLenos=0;
	float cantidadDeCasillerosTotales=(this->cantidadColumnas * this->cantidadFilas);

	for(unsigned int x=0;x<this->cantidadColumnas;x++){
		for (unsigned int y=0;y<this->cantidadFilas;y++){
			if (this->getCasillero(x,y).getEstado()) cantidadDeCasillerosLLenos++;
		}
	}
	return (cantidadDeCasillerosLLenos / cantidadDeCasillerosTotales);
}

void Tablero::mostrarPorConsola(){
	if((this->cantidadColumnas==0)||(this->cantidadFilas==0)){
		cout << "Tablero vacio!!"<< endl;
		return;
	}
	for(unsigned int j=0;j<this->cantidadFilas;j++){
		for(unsigned int i=0;i<this->cantidadColumnas;i++){
			if (this->tablero->obtenerCasillero(i,j).getEstado()) {
				cout << "X";
			}
			else {
				cout << "_";
			}
		}
		cout << endl;
	}
}

unsigned int Tablero::getAltura(){
	return this->cantidadFilas;
}

unsigned int Tablero::getAncho(){
	return this->cantidadColumnas;
}

Tablero::~Tablero() {

	delete this->tablero;

	while (!(piezasInsertadas.empty())){
		delete piezasInsertadas.front();
		piezasInsertadas.pop_front();
	}
}

Secuencia* Tablero::obtenerPiezasInsertadas(){
	Secuencia* secuenciaDePiezas;
	list<Pieza*>::iterator iteradorDeLista;
	Pieza* piezaEnLaSecuencia;

	secuenciaDePiezas=new Secuencia();

	if (piezasInsertadas.empty()) return secuenciaDePiezas;

	iteradorDeLista=piezasInsertadas.begin();
	for (unsigned int elemento=1;elemento <= piezasInsertadas.size();elemento++){
		piezaEnLaSecuencia= new Pieza(*(*iteradorDeLista));
		secuenciaDePiezas->agregarPieza(piezaEnLaSecuencia);
		iteradorDeLista++;
	}
	return secuenciaDePiezas;
}
